/*
 * $Id: QEExchangeResolver.m 248 2008-09-08 15:31:38Z jon $
 * Sleeper
 *
 * Created by Jon Gordon on 7/24/08
 * Copyright Jon Gordon.  All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#include <stdlib.h>

#import "Sleeper.h"

#import "QEExchangeResolver.h"
#import "QEExchangeElement.h"
#import "QETokenizer.h"


@interface QEExchangeResolver (Private)
- (NSMutableSet *)legalElementsForString:(NSString *)source;

// returns YES if the assignment locked out any possibilities, NO otherwise
- (BOOL)assignElement:(QEExchangeElement *)element
              toToken:(NSString *)token
          inMatchList:(NSMutableDictionary *)matchList;

- (void)setExchangeValid:(BOOL)newExchangeValid;

- (void)setElements:(NSDictionary *)newElements;
- (NSDictionary *)elements;

- (void)setSourceTokens:(NSDictionary *)newSourceTokens;
- (NSDictionary *)sourceTokens;

- (void)setResolvedElements:(NSDictionary *)newResolvedElements;
@end


//
// Attempts to resolve a list of strings (tokens) into elements of a
// November Sweepstakes exchange.
//
// Author: $Author: jon@bway.net $
// Version: $Revision: 2 $
//
@implementation QEExchangeResolver


static NSArray *elementNames;


+ (void)initialize
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        NSLog(@"initializing QEExchangeResolver");
        elementNames = [[NSArray alloc] initWithObjects:@"freq",
                        @"serialRcvd",
                        @"prec",
                        @"call",
                        @"check",
                        @"section",
                        nil];
    } // if
} // initialize


- (QEExchangeResolver *)init
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    self = [super init];
    
    if (nil != self) {
        exchangeValid = NO;
        
        NSMutableDictionary *tmpElements = [NSMutableDictionary dictionaryWithCapacity:[elementNames count]];
        NSEnumerator *e = [elementNames objectEnumerator];
        NSString *elementName;
        QEExchangeElement *element;
        while (elementName = [e nextObject]) {
            element = [QEExchangeElement elementForName:elementName];
            [tmpElements setObject:element forKey:elementName];
        } // while
        
        elements = [[NSDictionary alloc] initWithDictionary:tmpElements];
        resolvedElements = [[NSMutableDictionary alloc] init];
        sourceTokens = [[NSMutableDictionary alloc] init];
        tokenizer = [[QETokenizer alloc] init];
    } // if
    return self;
} // init


- (void)dealloc
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [tokenizer release];
    [elements release];
    [resolvedElements release];
    [sourceTokens release];
    [super dealloc];
} // dealloc


- (void)setExchangeValid:(BOOL)newExchangeValid
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    exchangeValid = newExchangeValid;
} // setExchangeValid:


- (void)setElements:(NSDictionary *)newElements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (elements != newElements) {
        [newElements retain];
        [elements autorelease];
        elements = newElements;
    } // if
} // setElements:


- (void)setResolvedElements:(NSDictionary *)newResolvedElements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (resolvedElements != newResolvedElements) {
        [resolvedElements autorelease];
        resolvedElements = [[NSDictionary alloc] initWithDictionary:newResolvedElements];
    } // if
} // setResolvedElements:

- (NSDictionary *)resolvedElements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[resolvedElements retain] autorelease];
} // resolvedElements

- (void)setSourceTokens:(NSDictionary *)newSourceTokens
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (sourceTokens != newSourceTokens) {
        [sourceTokens autorelease];
        sourceTokens = [[NSDictionary alloc] initWithDictionary:newSourceTokens];
    } // if
} // setSourceTokens:

- (NSDictionary *)sourceTokens
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[sourceTokens retain] autorelease];
} // sourceTokens

- (NSDictionary *)elements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[elements copy] autorelease];
} // elements


- (BOOL)isExchangeValid
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return exchangeValid;
} // isExchangeValid


+ (NSArray *)elementNames
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[elementNames copy] autorelease];
} // elementNames


- (BOOL)resolveExchange:(NSString *)inputExchange
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    
    if (inputExchange) {
        NSLog(@"  exchange: \"%@\"", inputExchange);
    } else {
        NSLog(@"  input exchange is nil--resetting to empty string");
        inputExchange = @"";
    } // if
    BOOL newExchangeValid = NO;
    
    NSMutableDictionary *newElements = nil;
    NSMutableDictionary *newSourceTokens = nil;
    NSArray *tokens = nil;
    
    if ([inputExchange length] > 0) {
        tokens = [tokenizer tokenize:inputExchange]; 
    } // if
    if ((nil != tokens) && ([tokens count] > 0)) {
        newElements = [NSMutableDictionary dictionaryWithCapacity:[tokens count]];
        newSourceTokens = [NSMutableDictionary dictionaryWithCapacity:[tokens count]];
        NSMutableDictionary *legalValuesForTokens = [NSMutableDictionary dictionaryWithCapacity:[tokens count]];
        
        NSString *token;
        NSEnumerator *tkEnum = [tokens objectEnumerator];
        while (nil != (token = [tkEnum nextObject])) {
            [legalValuesForTokens setObject:[self legalElementsForString:token] forKey:token];
        } // while
        
        // eliminate possibilities by locking in tokens and elements that have only
        // one possible match
        NSEnumerator *elNamesEnum;
        NSString *elName, *tkMatch;
        QEExchangeElement *element;
        NSSet *legalValues;
        int count;
        BOOL changed;
        do {
            changed = NO;
            tkEnum = [tokens objectEnumerator];
            while (nil != (token = [tkEnum nextObject])) {
                legalValues = [legalValuesForTokens objectForKey:token];
                if (1 == [legalValues count]) {
                    changed = [self assignElement:[legalValues anyObject] // OK--we know there's only 1 object 
                                          toToken:token 
                                      inMatchList:legalValuesForTokens];
                    if (changed) {
                        break;
                    } // if
                } // if
            } // while
            if (changed) {
                continue;
            } // if
            
            elNamesEnum = [elementNames objectEnumerator];
            while (nil != (elName = [elNamesEnum nextObject])) {
                element = [elements objectForKey:elName];
                count = 0;
                tkEnum = [tokens objectEnumerator];
                while (nil != (token = [tkEnum nextObject])) {
                    legalValues = [legalValuesForTokens objectForKey:token];
                    NSAssert1(nil != legalValues,
                              @"nil improperly returned as legal values for \"%@\"",
                              token);
                    if ([legalValues containsObject:element]) {
                        ++count;
                        tkMatch = token;
                    } // if
                } // while
                if (1 == count) {
                    changed = [self assignElement:element // OK--we know there's only 1 object 
                                          toToken:tkMatch 
                                      inMatchList:legalValuesForTokens];
                    if (changed) {
                        break;
                    } // if
                } // if
            } // while
            if (changed) {
                continue;
            } // if
            
            // finally, we apply a heuristic: first come, first served.  That is, when
            // multiple tokens match an element, we take the first token.  By putting
            // the elements in the order that we expect to see them, we should get
            // close enough in most cases.
            elNamesEnum = [elementNames objectEnumerator];
            while (nil != (elName = [elNamesEnum nextObject])) {
                element = [elements objectForKey:elName];
                tkEnum = [tokens objectEnumerator];
                while (nil != (token = [tkEnum nextObject])) {
                    if ([[legalValuesForTokens objectForKey:token] containsObject:element]) {
                        changed = [self assignElement:element
                                              toToken:token 
                                          inMatchList:legalValuesForTokens];
                        if (changed) {
                            break;
                        } // if
                    } // if
                } // while
                if (changed) {
                    break;
                } // if
            } // while
        } while (changed);
        
        // if the entry was valid, we have 1 matching token for each element, and
        // no extra tokens
        newExchangeValid = ([tokens count] == [elementNames count]);
        tkEnum = [tokens objectEnumerator];
        while (nil != (token = [tkEnum nextObject])) {
            if (1 == [[legalValuesForTokens objectForKey:token] count]) {
                element = [[legalValuesForTokens objectForKey:token] anyObject];
                elName = [element elementName];
                id newValue = [element valueForString:token];
                [newElements setObject:newValue forKey:elName];
                [newSourceTokens setObject:token forKey:newValue];
            } else {
                newExchangeValid = NO;
            } // else
        } // while
    } // if
    
#ifdef QE_VERBOSE_DEBUG
    {
        NSLog(@"  resolved elements:");
        NSEnumerator *debugElements = [newElements keyEnumerator];
        NSString *debugElementName;
        while (nil != (debugElementName = [debugElements nextObject])) {
            NSLog(@"    %@ : %@", 
                  debugElementName,
                  [newElements objectForKey:debugElementName]);
        } // while
    }
#endif
    
    [self setResolvedElements:newElements];
    [self setExchangeValid:newExchangeValid];
    [self setSourceTokens:newSourceTokens];
    return [self isExchangeValid];
} // resolveExchange:


- (NSString *)sourceTokenForResolvedElement:(id)resolvedElement
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[self sourceTokens] objectForKey:resolvedElement];
} // sourceTokenForResolvedElement:
@end


@implementation QEExchangeResolver (private)

- (NSMutableSet *)legalElementsForString:(NSString *)source
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSMutableSet *legalElements = [[NSMutableSet alloc] init];
    
    NSEnumerator *elEnum = [elementNames objectEnumerator];
    NSString *elName;
    QEExchangeElement *element;
    while (nil != (elName = [elEnum nextObject])) {
        element = [elements objectForKey:elName];
        if ([element isLegalValueInString:source]) {
            [legalElements addObject:element];
        } // if
    } // while
    
#ifdef QE_VERBOSE_DEBUG
    NSMutableString *elementNameList = [[[NSMutableString alloc] init] autorelease];
    elEnum = [legalElements objectEnumerator];
    while (nil != (element = [elEnum nextObject])) {
        [elementNameList appendFormat:@"%@ ", [element elementName]];
    } // while
    NSLog(@"  source \"%@\": %@", source, elementNameList);
#endif
    
    return legalElements;
} // legalElementsforString:

- (BOOL)assignElement:(QEExchangeElement *)element
              toToken:(NSString *)token
          inMatchList:(NSMutableDictionary *)matchList
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    BOOL changed = NO;
    
    // start with sanity checks
    if (!element) {
        NSLog(@"  WARNING: nil element, returning unchanged");
        return changed;
    } // if
    
    if (!token) {
        NSLog(@"  WARNING: nil token, returning unchanged");
        return changed;
    } // if
    
    if (!matchList) {
        NSLog(@"  WARNING: nil matchList, returning unchanged");
        return changed;
    } // if
    
    if (![matchList valueForKey:token]) {
        NSLog(@"  WARNING: nil matchList entry for token \"%@\", returning unchanged", token);
        return changed;
    } // if
    
    if (![[matchList valueForKey:token] containsObject:element]) {
        NSLog(@"  WARNING: element %@ has already been ruled out for token \"%@\", returning unchanged",
              [element elementName],
              token);
        return changed;
    } // if
    
    NSEnumerator *e;
    NSMutableSet *legalElements;
    NSString *excludeToken;
    
    legalElements = [matchList objectForKey:token];
    if (1 != [legalElements count]) {
        [legalElements removeAllObjects];
        [legalElements addObject:element];
        changed = YES;
    } // if
    
    e = [matchList keyEnumerator];
    while (nil != (excludeToken = [e nextObject])) {
        if (![excludeToken isEqual:token] && 
            [[matchList objectForKey:excludeToken] containsObject:element]) {
            [[matchList objectForKey:excludeToken] removeObject:element];
            changed = YES;
        } // if
    } // while
    
    return changed;
} // assignElement:toToken:inMatchList:
@end
